package it.antonio.ilpizze.reflection;

import it.antonio.leaves.util.Strings;

import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class Clazz<T> {

	private Class<T> clazz;
	
	protected Clazz(Class<T> clazz) {
		super();
		this.clazz = clazz;
	}
	
	public static <T> Clazz<T> get(Class<T> key) {
		return new Clazz<T>(key);
	}
	
	@SuppressWarnings("unchecked")
	public static <T> Clazz<T> get(T obj) {
		return new Clazz<T>((Class<T>) obj.getClass());
	}
	
	@SuppressWarnings("unchecked")
	public static <T> Clazz<T> get(String className) {
		try {
			Class<?> clazz = Class.forName(className);
			return new Clazz<T>((Class<T>) clazz);
		} catch (ClassNotFoundException e) {
			throw new ReflectionException(e.getMessage(), e);
		}
	}
	
	public List<ConstructorAccessor> findConstructors() {
		List<ConstructorAccessor> constructors = new LinkedList<ConstructorAccessor>();
		for (Constructor<?> c : clazz.getConstructors()) {
			constructors.add(new ConstructorAccessor(c));
		}
		return constructors;
	}
	
	public ConstructorAccessor findConstructor(Class<?>... classes) {
		Constructor<?> constr = null;
		try {
			constr = clazz.getConstructor(classes);
		} catch (SecurityException e) {
			throw new ReflectionException(e.getMessage(), e);
		} catch (NoSuchMethodException e) {
			throw new ReflectionException(e.getMessage(), e);
		}
		if(constr == null){
			return null;
		}
		ConstructorAccessor c = new ConstructorAccessor(constr);
		return c;
	}
	public List<MethodAccessor> findMethodsWithAnnotation(Class<? extends Annotation> annotation) {
		/*List<MethodAccessor> methods = new LinkedList<MethodAccessor>();
		
		Class<?> superClass = clazz.getSuperclass(); 
		if(superClass != null){
			Clazz.get(superClass).findMethodsWithAnnotation(annotation)
			methods.addAll();
		}
		
		for (Method method : clazz.getDeclaredMethods()) {
			if (method.isAnnotationPresent(annotation)) {
				methods.add(new MethodAccessor(method));
			}
		}
		return methods;*/
		
		final List<MethodAccessor> methods = new ArrayList<MethodAccessor>();
	    Class<?> classToScan = clazz;
	    while (classToScan != Object.class) { 
	        for (Method method : classToScan.getDeclaredMethods()) {
	            if (method.isAnnotationPresent(annotation)) {
	            	boolean add = true;
	            	for(MethodAccessor m: methods){
	            		Method original = m.getOriginalMethod();
	            		
	            		if (method.getName().equals(original.getName()) &&	Arrays.equals(method.getParameterTypes(), original.getParameterTypes())) {
	            			add = false;
							break;
						}
	            	}
	                if(add){
	                	methods.add(new MethodAccessor(method));
	                }
	            }
	        }
	        classToScan = classToScan.getSuperclass();
	    }
	    return methods;

	}
	
	
	
	
	
	
	public MethodAccessor findMethod(String name, Class<?>... args) {
		Method method;
		try {
			method = clazz.getDeclaredMethod(name, args);
		} catch (SecurityException e) {
			throw new ReflectionException(e.getMessage(), e);
		} catch (NoSuchMethodException e) {
			return null;
		}
		return new MethodAccessor(method);
	}
	
	
	public List<MethodAccessor> findMethod(String name) {
		List<MethodAccessor> methods = new ArrayList<MethodAccessor>();
		Method[] methodsArray = clazz.getDeclaredMethods();
		for(Method m: methodsArray){
			if(name.equals(m.getName())){
				methods.add(new MethodAccessor(m)); 
			}
		}
		 
		return methods;
	}

	public List<PropertyAccessor> findProperties() {
		List<PropertyAccessor> properties = new ArrayList<PropertyAccessor>();

		Map<String, Method> setters = new HashMap<String, Method>();
		Map<String, Method> getters = new HashMap<String, Method>();
		List<String> propertiesNames = new ArrayList<String>();

		for (Method candidateProperty : clazz.getDeclaredMethods()) {

			if (isGetter(candidateProperty)) {
				String name = getPropertyName(candidateProperty);
				if (!name.equals("class")) {
					getters.put(name, candidateProperty);
					if (!propertiesNames.contains(name)) {
						propertiesNames.add(name);
					}
				}
			}

			if (isSetter(candidateProperty)) {
				String name = getPropertyName(candidateProperty);
				setters.put(name, candidateProperty);
				if (!propertiesNames.contains(name)) {
					propertiesNames.add(name);
				}
			}
		}

		for (String name : propertiesNames) {
			MethodAccessor setter = null;
			Method mSetter = setters.get(name);
			if(mSetter != null) setter = new MethodAccessor(mSetter);
			
			MethodAccessor getter = null;
			Method mGetter = getters.get(name);
			if(mGetter != null) getter = new MethodAccessor(mGetter);
			
			properties.add(new PropertyAccessor(name, getter, setter));
		}
		return properties;
	}

	private static boolean isSetter(Method method) {
		return method.getName().startsWith("set")
				&& method.getParameterTypes().length == 1;
	}

	private static boolean isGetter(Method method) {
		return method.getName().startsWith("get")
				&& method.getParameterTypes().length == 0
				&& !method.getName().equals("getClass");
	}

	private static String getPropertyName(Method method) {
		String methodName = method.getName();
		String property = methodName.substring(3);
		return property.substring(0, 1).toLowerCase()
				+ property.substring(1, property.length());
	}

	public List<FieldAccessor> findFieldsWithAnnotation(Class<? extends Annotation> annotation) {
		List<FieldAccessor> fields = new LinkedList<FieldAccessor>();
		if (clazz.getSuperclass() != null) {
			Clazz<? super T> innerClass = Clazz.get(clazz.getSuperclass());
			fields.addAll(innerClass.findFieldsWithAnnotation(annotation));
		}
		for (Field field : clazz.getDeclaredFields()) {
			if (field.isAnnotationPresent(annotation)) {
				fields.add(new FieldAccessor(field));
			}
		}
		return fields;
		
	}
	public List<MethodAccessor> getMethods() {
		List<MethodAccessor> accessors = new ArrayList<MethodAccessor>();
		Method[] methods = clazz.getDeclaredMethods();
		for (Method method : methods) {
			accessors.add(new MethodAccessor(method));
		}
		return accessors;
	}
	public List<MethodAccessor> getMethodsNoSuperclass() {
		List<MethodAccessor> accessors = new ArrayList<MethodAccessor>();
		Method[] methods = clazz.getMethods();
		for (Method method : methods) {
			accessors.add(new MethodAccessor(method));
		}
		return accessors;
	}
	public MethodAccessor findSetter(String name) {
		String setterName = "set" + Strings.firstToUpperCase(name);
		for (Method candidate : clazz.getDeclaredMethods()) {
			if (candidate.getName().equals(setterName) && isSetter(candidate)) {
				return new MethodAccessor(candidate);
			}
		}
		return new MethodAccessor(null);
	}

	public FieldAccessor findFieldWithName(String name) {
		FieldAccessor field = null;
		try {
			field = new FieldAccessor(clazz.getDeclaredField(name));
		} catch (SecurityException e) {
			throw new ReflectionException(e.getMessage(), e);
		} catch (NoSuchFieldException e) {
		}
		
		if (field == null && clazz.getSuperclass() != null) {
			Clazz<? super T> innerClass = Clazz.get(clazz.getSuperclass());
			field = innerClass.findFieldWithName(name);
		}
		return field;
	}

	public <Y extends Annotation> Y getAnnotation(Class<Y> annClass) {
		return clazz.getAnnotation(annClass);
	}

	public <Y extends Annotation> AnnotationAccessor<Y> findAnnotation(Class<Y> annClass) {
		Y ann = getAnnotation(annClass);
		if(ann == null){
			 if(clazz.getSuperclass() != null){
				 return Clazz.get(clazz.getSuperclass()).findAnnotation(annClass);	 
			 }
			return null;
			
		} else {
			return new AnnotationAccessor<Y>(ann, clazz);	
		}
		
	}
	
	public List<Annotation> getAnnotations() {
		return Arrays.asList(clazz.getAnnotations());
	}

	public boolean isAnnotationPresent(Class<? extends Annotation> arg0) {
		return clazz.isAnnotationPresent(arg0);
	}

	public List<FieldAccessor> getFields()  {
		List<FieldAccessor> fields = new LinkedList<FieldAccessor>();
		if (clazz.getSuperclass() != null) {
			for (Field field : clazz.getSuperclass().getDeclaredFields()) {
				fields.add(new FieldAccessor(field));
			}
		}
		for (Field field : clazz.getDeclaredFields()) {
			fields.add(new FieldAccessor(field));
		}
		return fields;
	}

	public Class<T> getType() {
		return clazz;
	}

	public List<Class<?>> getInterfaces() {
		return Arrays.asList(clazz.getInterfaces());
	}

	public boolean isFinal() {
		return Modifier.isFinal(clazz.getModifiers());
	}

	public String getName() {
		return clazz.getName();
	}

	
	
	
	public InputStream getResourceAsStream(String arg0) {
		return clazz.getResourceAsStream(arg0);
	}

	public String getSimpleName() {
		return clazz.getSimpleName();
	}

	public T newInstance() {
		try {
			return clazz.newInstance();
		} catch (InstantiationException e) {
			throw new ReflectionException(e.getMessage(), e);
		} catch (IllegalAccessException e) {
			throw new ReflectionException(e.getMessage(), e);
		}
	}

	@Override
	public boolean equals(Object o) {
		return clazz.equals(o);
	}
	
	@Override
	public String toString() {
		return clazz.toString();
	}

	public List<Clazz<?>> getDeclaredClasses() {
		List<Clazz<?>> defineds = new LinkedList<Clazz<?>>();
		
		for (Class<?> defined : clazz.getDeclaredClasses()) {
			defineds.add(Clazz.get(defined));
		}
		return defineds;
	}

	public List<FieldAccessor> getDeclaredFields() {
		List<FieldAccessor> fields = new LinkedList<FieldAccessor>();
		for (Field field : clazz.getDeclaredFields()) {
			fields.add(new FieldAccessor(field));
		}
		return fields;
	}

	
	
	
}